// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2015 The Go Authors.  All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Package grpc outputs gRPC service descriptions in Go code.
// It runs as a plugin for the Go protocol buffer compiler plugin.
// It is linked in to protoc-gen-go.
package kit

//copy ../grpc/grpc.go ./grpc.go
//edit ./grpc.go --> grpc::init() like
//					//generator.RegisterPlugin(new(grpc))
//					generator.RegisterPlugin(&grpc{registry:new(zk)})
//					generator.RegisterPlugin(&grpc{registry:new(consul)})
//edit ./grpc.go --> grpc::Generate() add [g.generateRPC(file)] to end of func

import (
	"fmt"
	"github.com/golang/protobuf/protoc-gen-go/generator"
	pb "github.com/golang/protobuf/protoc-gen-go/descriptor"

)

type Regist interface {
	AddImport(g *grpc)
	Name() string
	GeneratorRegistClient(g *grpc,file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int)
	GeneratorRegistServer(g *grpc,file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int)
}

const (
	endpointPath = "github.com/go-kit/kit/endpoint"
	logPath = "github.com/go-kit/kit/log"

	grpcPath =  "github.com/go-kit/kit/transport/grpc"
	ggrpcPath = "google.golang.org/grpc"
	netPath = "net"


	sdPath = "github.com/go-kit/kit/sd"
	lbPath = "github.com/go-kit/kit/sd/lb"
	ioPath = "io"
	timePath = "time"

)

var grpcname generator.GoPackageName ="grpc"
var ggrpcname generator.GoPackageName ="grpc"
// Generate generates code for the services in the given file.
func (g *grpc) generateRPC(file *generator.FileDescriptor) {
	if len(file.FileDescriptorProto.Service) == 0 {
		return
	}

	g.registry.AddImport(g)

	g.gen.AddImport(endpointPath)
	g.gen.AddImport(contextPkgPath)
	g.gen.AddImport(logPath)

	grpcname = g.gen.AddImport(grpcPath)
	ggrpcname = g.gen.AddImport(ggrpcPath)
	g.gen.AddImport(netPath)

	g.gen.AddImport(sdPath)
	g.gen.AddImport(lbPath)
	g.gen.AddImport(ioPath)
	g.gen.AddImport(timePath)



	g.P("func __decodeRequest(_ context.Context, req interface{}) (interface{}, error) {")
	g.P("return req, nil")
	g.P("}")
	g.P("func __encodeResponse(_ context.Context, req interface{}) (interface{}, error) {")
	g.P("return req, nil")
	g.P("}")

	for i, service := range file.FileDescriptorProto.Service {



		g.generateRPCServer(file, service, i)
	}



	for i, service := range file.FileDescriptorProto.Service {



		g.generateRPCClient(file, service, i)
	}

}
func (g *grpc) generateRPCClient(file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int){
	origServName := service.GetName()
	fullServName := origServName
	if pkg := file.GetPackage(); pkg != "" {
		fullServName = pkg + "." + fullServName
	}
	servName := generator.CamelCase(origServName)




	g.registry.GeneratorRegistClient(g,file,service,index)


	g.P("ep := sd.NewEndpointer(inst, balance",servName,"reqF, l)")
	g.P("nlb := lb.NewRoundRobin(ep)")
	g.P("bep := lb.Retry(maxtry, timeout, nlb)")
	g.P("return &balance",servName,"{")
	g.P("bep:  bep,")
	g.P("svrs: make([]*clientItem",servName,", 0),")
	g.P("funs:[]func(",servName,"Client,context.Context,interface{},...grpc.CallOption)(interface{},error){")
	for _, method := range service.Method {
		g.P("call",servName,*method.Name,",")
	}
	g.P("},")
	g.P("}")
	g.P("}")

	for _, method := range service.Method {
		inType := g.typeName(method.GetInputType())
		g.P(fmt.Sprintf(`
			func call%s%s(client %sClient,ctx context.Context,req interface{},opts ...grpc.CallOption)(interface{},error)  {
				return client.%s(ctx,req.(*%s),opts...)
			}`,servName,*method.Name,servName,*method.Name,inType))
	}

	g.P(fmt.Sprintf(
`func balance%sreqF(serviceAddress string) (endpoint.Endpoint, io.Closer, error) {
			return func(ctx context.Context, request interface{}) (response interface{}, err error) {
				req := request.([]interface{})
				ag := req[0].(*balance%s)
		
				conn, e := ag.getOne(serviceAddress)
				if e != nil {
		
					return nil, e
				}
				opts :=req[3].([]grpc.CallOption)
				r,e:=ag.funs[req[1].(int)](conn,ctx,req[2],opts...)
		
				/*
				if e!=nil {
					conn,_= ag.next(conn)
					return ag.funs[req[1].(int)](conn,ctx,req[2],opts...)
				}
				*/
		
				return r,e
			}, nil, nil
		}`,servName,servName))

	g.P(`
	type balance`,servName,` struct {
	bep  endpoint.Endpoint
	svrs []*clientItem`,servName,`
	funs []func(`,servName,`Client,context.Context,interface{},...grpc.CallOption)(interface{},error)
	}`)


	g.P(fmt.Sprintf(`
		func (self *balance%s) next(c %sClient)(%sClient, error){
		
			if len(self.svrs) < 2{
				return c,nil
			}
			foundthis := false
			for {
				for _, v := range self.svrs {
					if foundthis {
						return v.con, nil
					}
					if v.con == c {
						foundthis = true
					}
				}
			}
		}`,servName,servName,servName))

	g.P(fmt.Sprintf(`func (self *balance%s) getOne(addr string) (%sClient, error) {	
	for _, v := range self.svrs {
		if v.addr == addr {
			return v.con, nil
		}
	}
	conn, err := grpc.Dial(addr, grpc.WithInsecure())

	if err != nil {
		return nil, err
	}
	cli := New%sClient(conn)
	self.svrs = append(self.svrs, &clientItem%s{
		addr: addr,
		idx:  len(self.svrs),
		con:  cli,
	})

	return cli, nil

}`,servName,servName,servName,servName))
	g.P(fmt.Sprintf(`type clientItem%s struct {
	con  %sClient
	addr string
	idx  int
	}`,servName,servName))


	for i, method := range service.Method {
		inType := g.typeName(method.GetInputType())
		outType := g.typeName(method.GetOutputType())
		g.P(fmt.Sprintf(`
		func (self *balance%s) %s(ctx context.Context,in *%s,opts ...grpc.CallOption) (*%s, error) {
			resp, e := self.bep(ctx, []interface{}{self, %d, in,opts})
			if e != nil {
				return nil, e
			}
			return resp.(*%s), nil
		}`,servName,*method.Name,inType,outType,i,outType))
	}



}
func (g *grpc) generateRPCServer(file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int){
	origServName := service.GetName()
	fullServName := origServName
	if pkg := file.GetPackage(); pkg != "" {
		fullServName = pkg + "." + fullServName
	}
	servName := generator.CamelCase(origServName)



	g.P("type _",servName,"Server struct{")
	ohandlerNames := make([]string,0)
	for _, method := range service.Method {
		//hname := g.generateServerMethod(servName, fullServName, method)
		hname := *method.Name+"Handler"
		ohandlerNames = append(ohandlerNames, hname)
		g.P(hname," ",grpcname,".Handler")
	}
	g.P("handle ",servName,"Server")
	g.P("}")


	for _, method := range service.Method {
		g.generateRPCServerMethod(servName, fullServName, method)
	}




	g.registry.GeneratorRegistServer(g,file,service,index)


	g.P("svr := new(_",servName,"Server)")
	g.P("svr.handle=server")

	for i, method := range service.Method {
		//hname := g.generateServerMethod(servName, fullServName, method)
		g.P(ohandlerNames[i]," := ",grpcname,".NewServer(")
		g.P("make",servName,*method.Name,"Endpoint(),")
		g.P("__decodeRequest,")
		g.P("__encodeResponse,")
		g.P(")")
		g.P("svr.",ohandlerNames[i],"=",ohandlerNames[i])

	}
	g.P(`ls, _ := net.Listen("tcp", bind)`)
	g.P("gs := ",ggrpcname,".NewServer()")
	g.P("Register",servName,"Server(gs, svr)")
	g.P("gs.Serve(ls)")
	g.P("}")
}


func (g *grpc) generateRPCServerMethod(servName, fullServName string, method *pb.MethodDescriptorProto) string {

	methName := generator.CamelCase(method.GetName())
	hname := fmt.Sprintf("_%s_%s_Handler", servName, methName)
	inType := g.typeName(method.GetInputType())
	outType := g.typeName(method.GetOutputType())

	g.P("func (s *_",servName,"Server) ",methName,"(ctx context.Context,in *",inType,") (*",outType,", error) {")
	g.P("_, rsp, err := s.",methName,"Handler.ServeGRPC(ctx, []interface{}{s.handle,in})")
	g.P("if err != nil {")
	g.P("return nil, err")
	g.P("}")
	g.P("return rsp.(*",outType,"), err")
	g.P("}")



	g.P("func make",servName,methName,"Endpoint() endpoint.Endpoint {")
	g.P("return func(ctx context.Context, request interface{}) (interface{}, error) {")
	g.P("hap := request.([]interface{})")
	g.P("return hap[0].(",servName,"Server).",methName,"(ctx,hap[1].(*",inType,"))")
	g.P("}")
	g.P("}")




	return hname
}